package main

import (
	"github.com/hyperledger/fabric/core/chaincode/shim"
	"fmt"
	pb "github.com/hyperledger/fabric/protos/peer"
	"bytes"
	"strconv"
	"errors"
	"strings"
	"math/big"
	"encoding/json"
	"time"
)

type TransactionChaincode struct {
}

type Transaction struct {
	Nonce  	int 		`json:"nonce"`
	Token   string 		`json:"token"`
	From	string 		`json:"from"`
	To		string 		`json:"to"`
	Value   *big.Int 	`json:"value"`
	Hash 	[]byte		`json:"hash"`
	Sign    []byte 		`json:"sign"`
	Data	[]byte 		`json:"data"`
	Time 	int64 		`json:"time"`
}

var log *shim.ChaincodeLogger

func main() {
	err := shim.Start(new(TransactionChaincode))
	if err != nil {
		fmt.Errorf(err.Error())
	}
}

func (t TransactionChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response {

	return shim.Success(nil)
}

func (t TransactionChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {

	fnc, args := stub.GetFunctionAndParameters()

	log = getLogger(stub)

	if fnc == "" {
		return shim.Error("function is nil")
	} else if fnc == "add" {
		return t.Add(stub, args)
	} else if fnc == "query" {
		return t.Query(stub, args)
	} else if fnc == "queryAll" {
		return t.QueryAll(stub, args)
	} else if fnc == "queryMyTokenTrans" {
		return t.QueryMyTokenTrans(stub, args)
	} else if fnc == "queryBySelector" {
		return t.QueryBySelector(stub, args)
	} else if fnc == "recoverTime" {
		return t.RecoverTime(stub, args)
	}

	return shim.Success(nil)
}

func (t TransactionChaincode) Add(stub shim.ChaincodeStubInterface, args []string) pb.Response {

	if len(args) != 2 {
		return shim.Error(parameterErr.Error())
	}
	key, tb := args[0], []byte(args[1])
	var trans Transaction
	err := json.Unmarshal(tb, &trans)
	if err != nil {
		return shim.Error(err.Error())
	}
	txtime, err := stub.GetTxTimestamp()
	trans.Time = txtime.Seconds
	log.Info(trans)
	tbs, err := json.Marshal(trans)
	if err != nil {
		return shim.Error(err.Error())
	}
	pri, err := stub.CreateCompositeKey(key, []string{strconv.Itoa(trans.Nonce), trans.To})
	if err := stub.PutState(pri, tbs); err != nil {
		return shim.Error(err.Error())
	}

	return shim.Success(nil)
}

func (t TransactionChaincode) Query(stub shim.ChaincodeStubInterface, args []string) pb.Response {

	if len(args) != 3 {
		return shim.Error(parameterErr.Error())
	}
	from, nonce, to := haxOxSuf(args[0]), args[1], haxOxSuf(args[2])
	key, err := stub.CreateCompositeKey(from, []string{nonce, to})
	if err != nil {
		return shim.Error(err.Error())
	}
	tb, err := stub.GetState(key)
	if err != nil {
		return shim.Error(err.Error())
	}

	return shim.Success(tb)
}

func (t TransactionChaincode) QueryBySelector(stub shim.ChaincodeStubInterface, args []string) pb.Response {
	if len(args) != 1 {
		return shim.Error(parameterErr.Error())
	}
	res, err := queryByStr(stub, args[0])
	if err != nil {
		return shim.Error(err.Error())
	}
	return shim.Success(res)
}

func (t TransactionChaincode) QueryMyTokenTrans(stub shim.ChaincodeStubInterface, args []string) pb.Response {
	if len(args) != 2 {
		return shim.Error(parameterErr.Error())
	}
	addr, token := haxOxSuf(args[0]), args[1]
	queryStr := `
{
	"selector": {
		"$or": [{
			"from": "%s"
		},{
			"to": "%s"
		}],
		"token": "%s"
	}
}`
	query := fmt.Sprintf(queryStr, addr, addr, token)
	log.Infof("queryString: %s", query)
	res, err := queryByStr(stub, query)
	if err != nil {
		return shim.Error(err.Error())
	}
	return shim.Success(res)
}

func (t TransactionChaincode) QueryAll(stub shim.ChaincodeStubInterface, args []string) pb.Response {
	if len(args) != 1 {
		return shim.Error(parameterErr.Error())
	}
	addr := haxOxSuf(args[0])
	queryStr := `
{
	"selector": {
		"$or": [{
			"from": "%s"
		},{
			"to": "%s"
		}]
	}
}`
	query := fmt.Sprintf(queryStr, addr, addr)
	log.Infof("queryString: %s", query)
	res, err := queryByStr(stub, query)
	if err != nil {
		return shim.Error(err.Error())
	}
	return shim.Success(res)
}

func (t TransactionChaincode) RecoverTime(stub shim.ChaincodeStubInterface, args []string) pb.Response {
	if len(args) != 8 {
		return shim.Error(parameterErr.Error())
	}
	from, nonceStr, token, to, valueStr, hash, sign, data := args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]
	nonce, err := strconv.Atoi(nonceStr)
	if err != nil {
		return shim.Error(err.Error())
	}
	value, _ := new(big.Int).SetString(valueStr, 10)
	if err != nil {
		return shim.Error(err.Error())
	}
	key, err := stub.CreateCompositeKey(from, []string{nonceStr, to})
	if err != nil {
		return shim.Error(err.Error())
	}
	trans := &Transaction{
		Nonce: nonce,
		Token: token,
		From:  from,
		To:    to,
		Value: value,
		Hash:  []byte(hash),
		Sign:  []byte(sign),
		Data:  []byte(data),
		Time:  time.Now().Unix(),
	}
	tb, err := json.Marshal(trans)
	if err != nil {
		return shim.Error(err.Error())
	}
	err = stub.PutState(key, tb)
	if err != nil {
		return shim.Error(err.Error())
	}
	return shim.Success(nil)
}

func queryByStr(stub shim.ChaincodeStubInterface, qst string) ([]byte, error) {
	reslist, err := stub.GetQueryResult(qst)
	if err != nil {
		return nil, err
	}
	return getListResult(reslist)
}

func haxOxSuf(addr string) string {
	if strings.Index(addr, "0x") == 0 {
		return strings.ToLower(addr[2:])
	}
	return strings.ToLower(addr)
}

var parameterErr = errors.New("parameter err")


/** 解析结果 */
func getListResult(resultsIterator shim.StateQueryIteratorInterface) ([]byte, error) {

	defer resultsIterator.Close()
	// buffer is a JSON array containing QueryRecords
	var buffer bytes.Buffer
	buffer.WriteString("[")

	bArrayMemberAlreadyWritten := false
	for resultsIterator.HasNext() {
		queryResponse, err := resultsIterator.Next()
		if err != nil {
			return nil, err
		}
		// Add a comma before array members, suppress it for the first array member
		if bArrayMemberAlreadyWritten == true {
			buffer.WriteString(",")
		}
		buffer.WriteString("{\"Key\":")
		buffer.WriteString("\"")
		buffer.WriteString(queryResponse.Key)
		buffer.WriteString("\"")

		buffer.WriteString(", \"Record\":")
		// Record is a JSON object, so we write as-is
		buffer.WriteString(string(queryResponse.Value))
		buffer.WriteString("}")
		bArrayMemberAlreadyWritten = true
	}
	buffer.WriteString("]")
	fmt.Printf("queryResult:\n%s\n", buffer.String())
	return buffer.Bytes(), nil
}

func getLogger(stub shim.ChaincodeStubInterface) (c *shim.ChaincodeLogger) {
	fcn, _ := stub.GetFunctionAndParameters()
	c = shim.NewLogger(fmt.Sprintf("%s.%s.%s", stub.GetChannelID(), "transaction", fcn))
	c.SetLevel(shim.LogDebug)
	return
}